package com.aboverock.integrations.redis.lock;

import java.lang.reflect.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * <p>
 * Redis分布式锁AOP
 * <p>
 *
 * @author RockerWang
 * @date Created in 2021/12/3 0:43
 */
@Aspect
@Component
@Slf4j
public class RedisLockAspect {

    @Autowired
    public RedisLockAspect(RedisLockUtil redisLockUtil) {
    }

    /**
     * 定义切点, RedisLockable注解的方法
     */
    @Pointcut("(execution(* com.aboverock..*(..)) && @annotation(com.aboverock.integrations.redis.lock.RedisLockable))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String targetName = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();

        if (method != null && method.isAnnotationPresent(RedisLockable.class)) {
            RedisLockable redisLock = method.getAnnotation(RedisLockable.class);
            String redisKey = getLockKey(targetName, methodName, redisLock.key());
            int expire = redisLock.expiration();
            int timeout = redisLock.timeout();
            boolean isLock = RedisLockUtil.lock(redisKey, expire, timeout);

            if (isLock) {
                try {
                    return point.proceed();
                } finally {
                    RedisLockUtil.unlock(redisKey);
                }
            } else {
                // 获取锁失败, 中断执行
                log.debug("Get redis lock fail.");
                return null;
            }
        }

        return point.proceed();
    }

    /**
     * 获取注解中的key值, 支持SpringExpression表达式
     *
     * @param targetName 目标类
     * @param methodName 目标方法
     * @param keys       SpELl中的key
     * @return redis key值
     */
    private String getLockKey(String targetName, String methodName, String[] keys) {

        StringBuilder sb = new StringBuilder();
        sb.append("lock:").append(targetName).append(":").append(methodName);

        if (keys != null) {
            String keyStr = StringUtils.join(keys, ':');
            sb.append("#").append(keyStr);
        }
        return sb.toString();
    }
}
